package evolution.data;

import java.util.ArrayList;
import java.util.List;

import evolution.algorithm.CoevolutionAlgorithm;
import evolution.algorithm.EvolutionaryAlgorithm;
import evolution.algorithm.Iterative;
import evolution.executor.EvolutionaryAlghorithmEpochRunner;
import evolution.individual.FitnessFunctionIndividualsFactory;
import evolution.individual.Individual;
import evolution.individual.IndividualsFactory;
import evolution.individual.RandomIndividualFactory;
import evolution.interceptor.ChartBuilderInterceptor;
import evolution.interceptor.CoevolutionIterationInterceptor;
import evolution.operation.migration.AbstractMigrationStrategy;
import evolution.operation.migration.OneSidedCircleMigration;
import evolution.operation.migration.TwoSidedCircleMigration;
import evolution.operation.mutation.AbstractMutation;
import evolution.operation.mutation.BoundaryMutation;
import evolution.operation.mutation.UniformMutation;
import evolution.operation.reproduction.AMXOReproduction;
import evolution.operation.reproduction.BLXaReproduction;
import evolution.operation.reproduction.ReproductionStrategy;
import evolution.operation.selection.RankSelection;
import evolution.operation.selection.RouletteWheelSelection;
import evolution.operation.selection.SelectionStrategy;
import evolution.operation.selection.TournamentSelection;
import evolution.operation.succession.BestSuccessionStrategy;
import evolution.operation.succession.FullSuccessionStrategy;
import evolution.operation.succession.SuccessionStrategy;
import evolution.problem.AckleyMinimization;
import evolution.problem.BeckerLagoMinimization;
import evolution.problem.Camelback2DMinimization;
import evolution.problem.GriewankMinimization;
import evolution.problem.MichalewiczMinimization;
import evolution.problem.OptimizationProblem;
import evolution.problem.RastriginMinimization;
import evolution.problem.Schaffer2DMinimization;
import evolution.problem.SchwefelMinimization;
import evolution.random.RandomGenerator;
import evolution.random.SFMT19937RandomGen;
import evolution.stop.IterationStopCondition;
import evolution.stop.StopCondition;
import evolution.stop.TimeStopCondition;

public class CoevolutionAlgorithmFactory {
	public CoevolutionAlgorithm create(CoevolutionDataBean coevolutionDataBean) {

		OptimizationProblem optimizationProblem = buildOptimizationProblem(coevolutionDataBean);

		int populationsCount = coevolutionDataBean.getPopulationsCount();
		List<EvolutionaryAlghorithmEpochRunner> evolutionaryAlghorithmEpochRunners = new ArrayList<EvolutionaryAlghorithmEpochRunner>(
				populationsCount);

		int populationSize = coevolutionDataBean.getPopulationSize();

		RandomGenerator random = new SFMT19937RandomGen();
		IndividualsFactory individualsFactory = new RandomIndividualFactory(
				optimizationProblem, random);

		for (int i = 0; i < populationsCount; i++) {
			EvolutionaryAlghorithmEpochRunner epochRunner = buildEvolutionaryAlgorithmEpochRunner(
					coevolutionDataBean, optimizationProblem, populationSize,
					individualsFactory);

			evolutionaryAlghorithmEpochRunners.add(epochRunner);
		}

		CoevolutionAlgorithm coevolutionAlgorithm = new CoevolutionAlgorithm();

		AbstractMigrationStrategy migrationStrategy = buildMigrationStrategy(coevolutionDataBean);
		coevolutionAlgorithm.setMigrationStrategy(migrationStrategy);

		StopCondition<Iterative> stopCondition = buildStopCondition(coevolutionDataBean);
		coevolutionAlgorithm.setStopCondition(stopCondition);

		coevolutionAlgorithm
				.setEvolutionaryAlghorithmEpochRunners(evolutionaryAlghorithmEpochRunners);

		CoevolutionIterationInterceptor interceptor = new ChartBuilderInterceptor();
		coevolutionAlgorithm.setInterceptor(interceptor);

		return coevolutionAlgorithm;
	}

	public CoevolutionAlgorithm create(String[] args) {
		Problem problem = Problem.valueOf(args[0].toUpperCase());
		System.out.println("Problem: " + problem);

		int dimensions = Integer.valueOf(args[1]);
		System.out.println("Dimensions: " + dimensions);

		int populationsCount = Integer.valueOf(args[2]);
		System.out.println("Populations count: " + populationsCount);

		int populationSize = Integer.valueOf(args[3]);
		System.out.println("Population size: " + populationSize);

		int epochLength = Integer.valueOf(args[4]);
		System.out.println("Epoch length: " + epochLength);

		int iterations = Integer.valueOf(args[5]);
		System.out.println("Iterations: " + iterations);

		Mutation mutation = Mutation.valueOf(args[6].toUpperCase());
		System.out.println("Mutation: " + mutation);

		double mutationProbability = Double.valueOf(args[7]);
		System.out.println("Mutation probability: " + mutationProbability);

		Reproduction crossover = Reproduction.valueOf(args[8].toUpperCase());
		System.out.println("Crossover: " + crossover);

		double crossoverProbability = Double.valueOf(args[9]);
		System.out.println("Crossover probability: " + crossoverProbability);

		Selection selection = Selection.valueOf(args[10].toUpperCase());
		System.out.println("Selection: " + selection);

		Migration migration = Migration.valueOf(args[11].toUpperCase());
		System.out.println("Migration: " + migration);

		int migrationSize = Integer.valueOf(args[12]);
		System.out.println("Migration size: " + migrationSize);

		CoevolutionDataBean coevolutionDataBean = new CoevolutionDataBean();
		coevolutionDataBean.setDimensions(dimensions);
		coevolutionDataBean.setEpochLength(epochLength);
		coevolutionDataBean.setMigration(migration);
		coevolutionDataBean.setMigrationParameter(migrationSize);
		coevolutionDataBean.setMutation(mutation);
		coevolutionDataBean.setMutationProbability(mutationProbability);
		coevolutionDataBean.setPopulationsCount(populationsCount);
		coevolutionDataBean.setPopulationSize(populationSize);
		coevolutionDataBean.setProblem(problem);
		coevolutionDataBean.setReproduction(crossover);
		coevolutionDataBean.setReproductionProbability(crossoverProbability);
		coevolutionDataBean.setSelection(selection);

		CoevolutionAlgorithm algorithm = new CoevolutionAlgorithmFactory()
				.create(coevolutionDataBean);

		IterationStopCondition stopCondition = new IterationStopCondition(
				iterations);

		algorithm.setStopCondition(stopCondition);

		algorithm.setInterceptor(null);

		return algorithm;
	}

	private EvolutionaryAlghorithmEpochRunner buildEvolutionaryAlgorithmEpochRunner(
			CoevolutionDataBean coevolutionDataBean,
			OptimizationProblem optimizationProblem, int populationSize,
			IndividualsFactory individualsFactory) {
		Individual[] population = new Individual[populationSize];
		for (int j = 0; j < populationSize; j++) {
			population[j] = individualsFactory.create();
		}

		RandomGenerator currentRandom = new SFMT19937RandomGen();

		EvolutionaryAlgorithm evolutionaryAlghorithm = new EvolutionaryAlgorithm();

		ReproductionStrategy reproductionStrategy = buildReproductionStrategy(
				coevolutionDataBean, optimizationProblem, currentRandom);

		evolutionaryAlghorithm.setReproductor(reproductionStrategy);
		evolutionaryAlghorithm.setOptimizationProblem(optimizationProblem);

		AbstractMutation mutationStrategy = buildMutationStrategy(
				coevolutionDataBean, optimizationProblem, currentRandom);

		evolutionaryAlghorithm.setMutator(mutationStrategy);
		evolutionaryAlghorithm.setPopulation(population);

		SelectionStrategy selectionStrategy = buildSelectionStrategy(
				coevolutionDataBean, currentRandom);
		evolutionaryAlghorithm.setSelector(selectionStrategy);

		SuccessionStrategy successionStrategy = buildSuccessionStrategy(coevolutionDataBean);
		evolutionaryAlghorithm.setSuccessor(successionStrategy);

		EvolutionaryAlghorithmEpochRunner epochRunner = new EvolutionaryAlghorithmEpochRunner();
		epochRunner.setEpochLength(coevolutionDataBean.getEpochLength());
		epochRunner.setEvolutionaryAlghorithm(evolutionaryAlghorithm);
		return epochRunner;
	}

	private StopCondition<Iterative> buildStopCondition(
			CoevolutionDataBean coevolutionDataBean) {
		StopCondition<Iterative> stopCondition = null;
		switch (coevolutionDataBean.getStop()) {
		case ITERATIONS:
			stopCondition = new IterationStopCondition();
			break;
		case TIME:
			stopCondition = new TimeStopCondition();
		}
		stopCondition.setParameter(coevolutionDataBean.getStopParameter());
		return stopCondition;
	}

	private AbstractMigrationStrategy buildMigrationStrategy(
			CoevolutionDataBean coevolutionDataBean) {
		AbstractMigrationStrategy migrationStrategy = null;
		switch (coevolutionDataBean.getMigration()) {
		case ONE_SIDED_CIRCLE:
			migrationStrategy = new OneSidedCircleMigration();
			break;
		case TWO_SIDED_CIRCLE:
			migrationStrategy = new TwoSidedCircleMigration();
		}
		migrationStrategy.setMigrationSize(coevolutionDataBean
				.getMigrationParameter());
		return migrationStrategy;
	}

	private SuccessionStrategy buildSuccessionStrategy(
			CoevolutionDataBean coevolutionDataBean) {
		SuccessionStrategy successionStrategy = null;
		switch (coevolutionDataBean.getSuccession()) {
		case FULL:
			successionStrategy = new FullSuccessionStrategy();
			break;
		case BEST:
			successionStrategy = new BestSuccessionStrategy();
			break;
		}
		return successionStrategy;
	}

	private SelectionStrategy buildSelectionStrategy(
			CoevolutionDataBean coevolutionDataBean,
			RandomGenerator currentRandom) {
		SelectionStrategy selectionStrategy = null;
		switch (coevolutionDataBean.getSelection()) {
		case TOURNAMENT:
			TournamentSelection tournamentSelection = new TournamentSelection();
			tournamentSelection.setRandomGenerator(currentRandom);
			tournamentSelection.setTournamentSize(2);
			selectionStrategy = tournamentSelection;
			break;
		case ROULETTE_WHEEL:
			RouletteWheelSelection rouletteWheelSelection = new RouletteWheelSelection();
			rouletteWheelSelection.setRandomGenerator(currentRandom);
			selectionStrategy = rouletteWheelSelection;
			break;
		case RANKING:
			RankSelection rankSelection = new RankSelection();
			rankSelection.setRandomGenerator(currentRandom);
			selectionStrategy = rankSelection;
		}
		return selectionStrategy;
	}

	private OptimizationProblem buildOptimizationProblem(
			CoevolutionDataBean coevolutionDataBean) {
		OptimizationProblem optimizationProblem = null;
		switch (coevolutionDataBean.getProblem()) {
		case RASTRIGIN:
			optimizationProblem = new RastriginMinimization();
			break;
		case GRIEWANK:
			optimizationProblem = new GriewankMinimization();
			break;
		case SCHWEFEL:
			optimizationProblem = new SchwefelMinimization();
			break;
		// case SHUBERT:
		// optimizationProblem = new ShubertMinimization();
		// break;
		case ACKLEY:
			optimizationProblem = new AckleyMinimization();
			break;
		case MICHALEWICZ:
			optimizationProblem = new MichalewiczMinimization();
			break;
		case CAMELBACK:
			optimizationProblem = new Camelback2DMinimization();
			break;
		case BECKER_LAGO:
			optimizationProblem = new BeckerLagoMinimization();
			break;
		case SCHAFFER2:
			optimizationProblem = new Schaffer2DMinimization();
			break;
		}
		optimizationProblem.getConstraints().setDimensions(
				coevolutionDataBean.getDimensions());
		return optimizationProblem;
	}

	private AbstractMutation buildMutationStrategy(
			CoevolutionDataBean coevolutionDataBean,
			OptimizationProblem optimizationProblem,
			RandomGenerator currentRandom) {

		AbstractMutation mutationStrategy = null;
		switch (coevolutionDataBean.getMutation()) {
		case BOUNDARY:
			mutationStrategy = new BoundaryMutation();
			break;
		case UNIFORM:
			mutationStrategy = new UniformMutation();
		}

		mutationStrategy.setProbability(coevolutionDataBean
				.getMutationProbability());
		mutationStrategy.setConstraints(optimizationProblem.getConstraints());
		mutationStrategy.setRandomGenerator(currentRandom);
		return mutationStrategy;
	}

	private ReproductionStrategy buildReproductionStrategy(
			CoevolutionDataBean coevolutionDataBean,
			OptimizationProblem optimizationProblem,
			RandomGenerator currentRandom) {
		ReproductionStrategy reproductionStrategy = null;
		FitnessFunctionIndividualsFactory fitnessFunctionIndividualsFactory = new FitnessFunctionIndividualsFactory(
				optimizationProblem.getFitnessFunction());

		switch (coevolutionDataBean.getReproduction()) {
		case AMXO:
			AMXOReproduction amxo = new AMXOReproduction();
			amxo.setIndividualsFactory(fitnessFunctionIndividualsFactory);
			reproductionStrategy = amxo;
			break;
		case BLXa:
			BLXaReproduction blxao = new BLXaReproduction();
			blxao.setIndividualsFactory(fitnessFunctionIndividualsFactory);
			reproductionStrategy = blxao;
			break;
		}
		reproductionStrategy.setRandomGenerator(currentRandom);
		reproductionStrategy.setProbability(coevolutionDataBean
				.getReproductionProbability());

		return reproductionStrategy;
	}
}
